package twiter.snowflake;


import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.util.Random;

/**
 * SnowflakeIdFactory
 *
 * @author Tong
 * @date 2018/06/06
 */
public class SnowflakeIdFactory {

    private String workIdMapStr;

    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long machineId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdFactory(String workIdMapStr) {
        this.workIdMapStr = workIdMapStr;
    }

    /**
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器调用一次，类似于Serclet的inti()方法。
     * 被@PostConstruct修饰的方法会在构造函数之后，init()方法之前运行。
     */
    //@PostConstruct
    private void init() {
        String string = null;
        if (StringUtils.isNotEmpty(workIdMapStr)) {
            try {
                JSONObject workIdJson = JSONObject.parseObject(workIdMapStr);
                String address = InetAddress.getLocalHost().getHostAddress();
                //String ip = InetAddress.getLocalHost().getHostAddress().toString();
                string = workIdJson.getString(address);
                if (string != null) {
                    workerId = Long.valueOf(String.valueOf(string));
                }

            } catch (Exception e) {
                throw new IllegalArgumentException(String.format("SnowflakeIdFactory workerId 格式不正确:%s", string));
            }

            this.datacenterId = (workerId & 0b1111100000) >> 5;
            this.machineId = workerId & 0b0000011111;

        } else {
            throw new IllegalArgumentException(String.format("SnowflakeIdFactory workerId 没有正确配置:%s", string));
        }

    }

    /**
     * 获取下一个id(十进制)
     *
     * @return
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (machineId << workerIdShift) | sequence;
    }

    /**
     * 获取下一个id(十六进制)
     *
     * @return
     */
    public String nextIdHexStr() {
        long l = nextId();
        return Long.toHexString(l).length() == 15 ? "0" + Long.toHexString(l) : Long.toHexString(l);
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {

        String workIdMapStr = "";
        SnowflakeIdFactory snowflakeIdFactory = new SnowflakeIdFactory(workIdMapStr);
        for (int workerId = 1024; workerId <= 2047; workerId++) {
            System.out.println(snowflakeIdFactory.nextId());
//            long datacenterId = (workerId & 0b1111100000) >> 5;
//            long machineId = workerId & 0b0000011111;
//            System.out.println(datacenterId + " " + machineId);
        }

    }
}
